package com.boot.security.service.database.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.boot.persistence.service.association.AssociationServiceImpl;
import com.boot.security.mapper.AccessMapper;
import com.boot.security.mapper.AccessResourceMapper;
import com.boot.security.mapper.ResourceMapper;
import com.boot.security.model.po.Access;
import com.boot.security.model.po.AccessResource;
import com.boot.security.model.po.Resource;
import com.boot.security.model.vo.SourceKey;
import com.boot.security.service.authorization.AccessSource;
import com.boot.security.service.authorization.AccessSourceClusterService;
import com.boot.security.service.authorization.AntPathRequestMatcher;
import com.boot.security.service.database.AccessResourceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2020/2/18 17:40
 **/
@Slf4j
@Service
public class AccessResourceServiceImpl extends AssociationServiceImpl<Long, Long, Long, AccessResource, Access, Resource,
        AccessResourceMapper, AccessMapper, ResourceMapper>
        implements AccessResourceService {

    private final AccessSource accessSource;

    private final AccessSourceClusterService[] accessSourceHandlers;

    public AccessResourceServiceImpl(AccessResourceMapper mapper, AccessMapper accessMapper, ResourceMapper resourceMapper,
                                     AccessSource accessSource, @Autowired(required = false) AccessSourceClusterService[] accessSourceHandlers) {
        super(mapper, accessMapper, resourceMapper);
        this.accessSource = accessSource;
        if (accessSourceHandlers == null) {
            log.info("There is no IAccessSourceHandler for updating AccessSource, the application is standalone!");
        }
        this.accessSourceHandlers = accessSourceHandlers;
    }

    @Override
    public Set<Access> searchResourceAccess(Long id) {
        List<AccessResource> list = super.list(Wrappers.query(AccessResource.builder().resourceId(id).build()));
        Set<Long> accessIds = list.stream()
                .map(AccessResource::getAccessId)
                .collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(accessIds)) {
            List<Access> accesses = getMMapper().selectBatchIds(accessIds);
            return new HashSet<>(accesses);
        } else {
            return Collections.emptySet();
        }
    }

    @Override
    public Set<String> searchPathAuthority(String path) {
        Set<String> result = new HashSet<>();
        accessSource.getReadLock().lock();
        try {
            for (Map.Entry<AntPathRequestMatcher, Set<String>> entry : accessSource.getMenuUrlSource().entrySet()) {
                if (entry.getKey().matches(path)) {
                    result.addAll(entry.getValue());
                }
            }
            return result;
        } finally {
            accessSource.getReadLock().unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean save(AccessResource model) {
        List<Long> accessIds = mapper
                .selectList(Wrappers.query(AccessResource.builder().resourceId(model.getResourceId()).build()))
                .stream().map(AccessResource::getAccessId).collect(Collectors.toList());
        Set<Long> insertIds = new HashSet<>();
        save(model.getResourceId(), model.getAccessId(), new HashSet<>(accessIds), insertIds);
        if (!CollectionUtils.isEmpty(insertIds)) {
            SourceKey sourceKey = accessSource.saveOne(model.getResourceId());
            for (AccessSourceClusterService accessSourceHandler : accessSourceHandlers) {
                accessSourceHandler.saveOne(sourceKey, model.getResourceId());
            }
            return true;
        } else {
            return false;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<AccessResource> entityList) {
        Set<Long> insertIds = new HashSet<>();
        entityList.forEach(entity -> {
            List<Long> accessIds = mapper
                    .selectList(Wrappers.query(AccessResource.builder().resourceId(entity.getResourceId()).build()))
                    .stream().map(AccessResource::getAccessId).collect(Collectors.toList());
            save(entity.getResourceId(), entity.getAccessId(), new HashSet<>(accessIds), insertIds);
        });
        if (!CollectionUtils.isEmpty(insertIds)) {
            Set<Long> resourceIds = mapper.selectBatchIds(insertIds)
                    .stream().map(AccessResource::getResourceId).collect(Collectors.toSet());
            SourceKey sourceKey = accessSource.saveMulti(resourceIds);
            for (AccessSourceClusterService accessSourceHandler : accessSourceHandlers) {
                accessSourceHandler.saveMulti(sourceKey, new ArrayList<>(resourceIds));
            }
            return true;
        } else {
            return false;
        }
    }

    public void save(Long resourceId, Long accessId, Set<Long> existAccesses, Set<Long> insertIds) {
        if (!existAccesses.contains(accessId)) {
            AccessResource model = AccessResource.builder()
                    .resourceId(resourceId)
                    .accessId(accessId)
                    .build();
            mapper.insert(model);
            existAccesses.add(accessId);
            insertIds.add(model.getId());
            List<Access> children = getMMapper()
                    .selectList(Wrappers.query(Access.builder()
                            .parent(accessId)
                            .build()));
            if (children != null && children.size() > 0) {
                children.forEach(access -> save(resourceId, access.getId(), existAccesses, insertIds));
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeById(Long id) {
        AccessResource accessResource = mapper.selectById(id);
        if (accessResource == null) {
            return false;
        } else {
            List<AccessResource> exist = mapper
                    .selectList(Wrappers.query(AccessResource.builder().resourceId(accessResource.getResourceId()).build()));
            Access access = getMMapper().selectById(accessResource.getAccessId());
            Set<Long> deleteIds = new HashSet<>();
            delete(accessResource.getResourceId(), access, new HashSet<>(exist), deleteIds);
            if (!CollectionUtils.isEmpty(deleteIds)) {
                SourceKey sourceKey = accessSource.saveOne(accessResource.getResourceId());
                for (AccessSourceClusterService accessSourceHandler : accessSourceHandlers) {
                    accessSourceHandler.saveOne(sourceKey, accessResource.getResourceId());
                }
                return true;
            } else {
                return false;
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean removeByIds(Collection<? extends Long> idList) {
        Set<Long> resourceIds = new HashSet<>();
        idList.forEach(id -> {
            AccessResource accessResource = mapper.selectById(id);
            List<AccessResource> exist = mapper
                    .selectList(Wrappers.query(AccessResource.builder().resourceId(accessResource.getResourceId()).build()));
            Access access = getMMapper().selectById(accessResource.getAccessId());
            Set<Long> deleteIds = new HashSet<>();
            delete(accessResource.getResourceId(), access, new HashSet<>(exist), deleteIds);
            if (!CollectionUtils.isEmpty(deleteIds)) {
                resourceIds.add(accessResource.getResourceId());
            }
        });
        if (!CollectionUtils.isEmpty(resourceIds)) {
            SourceKey sourceKey = accessSource.saveMulti(resourceIds);
            for (AccessSourceClusterService accessSourceHandler : accessSourceHandlers) {
                accessSourceHandler.saveMulti(sourceKey, new ArrayList<>(resourceIds));
            }
            return true;
        } else {
            return false;
        }
    }

    public void delete(Long resourceId, Access access, Set<AccessResource> existAccesses, Set<Long> deleteIds) {
        Set<AccessResource> filter = existAccesses.stream().filter(model -> model.getResourceId().equals(resourceId)
                && model.getAccessId().equals(access.getId())).collect(Collectors.toSet());
        if (filter.size() > 0) {
            Set<Long> idList = filter.stream().map(AccessResource::getId).collect(Collectors.toSet());
            super.removeByIds(idList);
            existAccesses.removeAll(filter);
            deleteIds.addAll(idList);
            Access parent = getMMapper().selectById(access.getParent());
            if (parent != null) {
                delete(resourceId, parent, existAccesses, deleteIds);
            }
        }
    }

    @Override
    public AccessResource createModel() {
        return new AccessResource();
    }

    @Override
    public Access createMModel() {
        return new Access();
    }

    @Override
    public Resource createNModel() {
        return new Resource();
    }
}
